<style scoped lang="less">
.outside-container {
  width: 100%;
  //max-width: 800px;
  height: 100%;
  //max-height: 600px;
  position: relative;
  display: flex;

  .icon {
    position: absolute;
    right: 20px;
    top: 20px;
    cursor: pointer;
    font-size: 24px;
  }

  /deep/ .el-footer {
    height: 20% !important;
  }

  .footer {
    border: 1px solid #eeeeee;
  }
}

.ant-flow {
  width: 100%;
  height: 100%;
  display: flex;
  overflow: hidden;
  position: relative;

  .flow-library {
    min-width: 230px;
    flex: 1;
  }

  .operator {
    flex: 6;
  }

  #container {
    //flex: 1;
    width: 100%;
    height: calc(100% - 38px);
  }

  .ant-flow-save {
    width: 252px;
    position: absolute;
    left: 0;
    bottom: 0;
  }
}

.toolbar {
  width: 100%;
  height: 38px;
  background-color: #f7f9fb;
  border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
</style>

<template>
  <el-container class="outside-container">
    <el-main>
      <div class="ant-flow">
        <flow-library class="flow-library" @onAddNode="onAddNode" />
        <div class="operator">
          <flow-tool class="toolbar" :graph="graphObj" />
          <div id="container"></div>
        </div>
        <flow-content-menu
          :visible.sync="menuVisible"
          :position="menuPosition"
          :clickedNodeId="clickedNodeId"
          @onMenuClick="onMenuClick"
        />
      </div>
    </el-main>
    <el-footer class="footer">
      <console-component></console-component>
    </el-footer>
    <mine-dialog
      :top="'10vh'"
      :appendToBody="true"
      :dialogFormVisible="flag"
      :width="'900px'"
      :modalFlag="modalFlag"
      :title="title"
      :showClose="showClose"
      @close="close"
    >
      <InputOpera
        v-if="assembly === '输入文件'"
        slot="option"
        :flag.sync="flag"
        @close="close"
        @changeFlag="close()"
        :currentOperatorData="currentOperatorData"
        :currentProcessId="resultOpr.data[0].id"
      ></InputOpera>
      <OutputOpera
        v-if="assembly === '输出文件'"
        slot="option"
        :flag="flag"
        @close="close"
      ></OutputOpera>
      <Default
        v-if="assembly === '缺省值处理'"
        slot="option"
        :flag="flag"
        @close="close"
      ></Default>
      <Attrs
        v-if="assembly === '属性生成'"
        slot="option"
        :flag="flag"
        @close="close"
      ></Attrs>
      <Deduplication
        v-if="assembly === '数据去重'"
        slot="option"
        :flag="flag"
        @close="close"
      ></Deduplication>
      <filterOpera
        v-if="assembly === '过滤算子'"
        slot="option"
        :flag="flag"
        @close="close"
      ></filterOpera>
      <Pca
        v-if="assembly === '主成分分析'"
        slot="option"
        :flag="flag"
        @close="close"
      ></Pca>
      <Standard
        v-if="assembly === '数据标准化'"
        slot="option"
        :flag="flag"
        @close="close"
      ></Standard>
    </mine-dialog>
  </el-container>
</template>

<script>
import { Graph, Addon, DataUri } from "@antv/x6";

import FlowContentMenu from "./components/FlowContentMenu";
import FlowLibrary from "./components/FlowLibrary";
import FlowTool from "./components/FlowTool";
import ConsoleComponent from "./components/Console/Console";
import InputOpera from "./components/Dialog/InputOpera";
import OutputOpera from "./components/Dialog/OutputOpera";
import Default from "./components/Dialog/Default";
import Attrs from "./components/Dialog/Attrs";
import Deduplication from "./components/Dialog/Deduplication";
import filterOpera from "./components/Dialog/filterOpera";
import Pca from "./components/Dialog/Pca";
import Standard from "./components/Dialog/Standard";

import {
  graphOptions,
  ports,
  SETTING_SHAPE_NAME,
  SettingNodeOptions,
  colors,
  DAG_NODE_Options,
  DAG_NAME_GROUP,
} from "./config";
import { detailMixins, modalMixins } from "./mixins.js";
import { $ } from "../../utils/tool";
import qs from "qs";

let graph,
  dnd,
  selector = null;

export default {
  name: "FlowChart",
  mixins: [detailMixins, modalMixins],
  components: {
    FlowLibrary,
    FlowContentMenu,
    FlowTool,
    ConsoleComponent,

    InputOpera,
    OutputOpera,
    Default,
    Attrs,
    Deduplication,
    filterOpera,
    Pca,
    Standard,
  },
  data() {
    return {
      call: "",
      rawSource: {},
      menuVisible: false,
      menuPosition: {},
      graphObj: null,
      clickedNodeId: "",
      assembly: "",
      resultOpr: "",
      resultLine: "",
      dynamicOperatorList: [], // 加入画布中的算子
      currentOperatorData: {}, // 当前操作的算子
    };
  },
  watch: {
    clickedNodeId: {
      handler() {
        this.select = [];
      },
    },
    rawSource: {
      handler(newVal) {
        graph.fromJSON(newVal);
      },
    },
  },
  beforeCreate() {
    this.call = "";
  },
  created() {
    this.$store.commit("SET_BREADCRUMB", [
      { title: "特征工程", path: "/featureEngineering" },
      { title: "详情", path: "" },
    ]);
    this.init();
  },
  beforeDestroy() {
    //    画布销毁及回收
    graph && graph.dispose();
    graph = null;
    dnd = null;
    selector = null;
  },
  methods: {
    /**
     * 确保container存在
     */
    init() {
      if (document.getElementById("container") === null) {
        const judgeGraphDiv = setInterval(() => {
          if (document.getElementById("container") !== null) {
            window.clearInterval(judgeGraphDiv);
            this.drawGraph();
          }
        }, 1000);
      } else {
        this.drawGraph();
      }
    },
    /**
     * 初始化画布
     */
    drawGraph() {
      graph = new Graph(
        graphOptions(
          false,
          $("#container").getBoundingClientRect().width,
          $("#container").getBoundingClientRect().height
        )
      );

      // 注册自定义节点
      this.registerNode();

      // 读取配置
      // this.getFeCanvasById()
      this.getFeCanvas();
      graph.fromJSON(this.rawSource);

      // 居中展示
      graph.centerContent();

      // 快捷键
      this.initEvent();

      // 拖拽init
      const options = {
        target: graph,
        scaled: false,
        animation: true,
      };
      dnd = new Addon.Dnd(options);

      this.graphObj = graph;
    },
    /**
     * 注册自定义节点
     */
    registerNode() {
      for (let prop in DAG_NAME_GROUP) {
        // console.log(DAG_NAME_GROUP[prop], ...DAG_NODE_Options[prop])
        Graph.registerNode(
          DAG_NAME_GROUP[prop],
          {
            ...DAG_NODE_Options[prop],
          },
          true
        );
      }
      Graph.registerNode(
        SETTING_SHAPE_NAME,
        {
          ...SettingNodeOptions,
        },
        true
      );
    },
    /**
     * 快捷键与事件
     */
    initEvent() {
      // 点击...
      graph.on("cell:click", (e) => {
        this.menuVisible = false;
      });
      graph.on("node:click", (e) => {
        this.menuVisible = false;
        // console.log(e)
        const { x, y, node, view, cell } = e;
        const data = node.getData();
        switch (data.id) {
          case "input": {
            this.$store.commit("SET_INTRODUCTION", "输入文件");
            break;
          }
          case "output": {
            this.$store.commit("SET_INTRODUCTION", "输出文件");
            break;
          }
          case "filterOpera": {
            this.$store.commit("SET_INTRODUCTION", "过滤算子");
            break;
          }
          case "attrs": {
            this.$store.commit("SET_INTRODUCTION", "属性生成");
            break;
          }
          case "standard": {
            this.$store.commit("SET_INTRODUCTION", "数据标准化");
            break;
          }
          case "deduplication": {
            this.$store.commit("SET_INTRODUCTION", "数据去重");
            break;
          }
          case "pca": {
            this.$store.commit("SET_INTRODUCTION", "主成分分析");
            break;
          }
          case "default": {
            this.$store.commit("SET_INTRODUCTION", "缺省值处理");
            break;
          }
        }
      });
      graph.on("node:added", (e) => {
        this.feOperatorSaveOrUpdate(e.cell);
      });
      graph.on("edge:added", (e) => {
        console.log("edge:added", e);
      });
      // 双击动态添加链接桩（取消该功能）
      // graph.on('node:dblclick', e => {
      //     const {e: event, node} = e
      //     const shape = e.node.shape
      //     // 当前选中元素
      //     const $select =
      //         shape === SETTING_SHAPE_NAME
      //             ? document.querySelector('.x6-node-selected > rect')
      //             : document.querySelector(`.x6-node-selected > ${shape}`)
      //     if (!$select) {
      //         return
      //     }
      //     const position = $select.getBoundingClientRect && $select.getBoundingClientRect()
      //     if (!position) {
      //         return
      //     }
      //     // 鼠标位置
      //     const pageX = event.pageX
      //     const pageY = event.pageY
      //     const zoom = graph.zoom()
      //     // 相对节点左上角的位置
      //     const x = (pageX - position.x) / zoom
      //     const y = (pageY - position.y) / zoom
      //     node.addPort({
      //         group: 'absolute',
      //         args: {
      //             // 计算链接桩位置
      //             x: Math.round(x),
      //             y: Math.round(y)
      //         },
      //         silent: false
      //     })
      // })

      graph.on("edge:connected", ({ isNew, edge }) => {
        console.log("edge:connected", edge);
        if (isNew) {
          this.feLineSaveOrUpdate(edge.store.data);
        }
      });
      graph.on("edge:removed", ({ cell, index, options }) => {
        console.log("edge:removed", cell);
      });
      graph.on("node:removed", ({ cell, index, options }) => {
        console.log("node:removed", cell);
      });
      graph.on("node:moved", ({ e, x, y, node, view }) => {
        this.feOperatorSaveOrUpdate(node);
      });
      graph.on("edge:moved", ({ e, x, y, node, view }) => {
        console.log("edge:moved", e);
      });
      graph.on("cell:mouseenter", ({ cell }) => {
        let self = this;
        if (cell.isEdge()) {
          cell.addTools([
            {
              name: "button-remove",
              args: {
                x: "30%",
                y: "50%",
                onClick({ view }) {
                  console.log(view, "button-remove: view");
                  console.log(self.resultLine, "this.resultLine");
                  const ary = self.resultLine.data.filter(
                    (item) => item.id == self.extractNumbers(view.cell.id)
                  );
                  self.deleteLine(ary[0]);
                  view.remove();
                  self.getFeCanvas();
                },
              },
            },
          ]);
        }
      });
      graph.on("cell:mouseleave", ({ cell }) => {
        if (cell.isEdge()) {
          cell.removeTool("button-remove");
        }
      });

      // 链接桩控制
      graph.on("node:mouseenter", () => {
        this.showPorts(true);
      });
      graph.on("node:mouseleave", () => {
        this.showPorts(false);
      });

      // 右键菜单
      graph.on("cell:contextmenu", (e) => {
        const { e: event, cell } = e;
        // const node = graph.getModel()
        // 画布节点
        selector = cell;
        this.clickedNodeId = selector.data.id;
        this.menuVisible = false;
        this.menuPosition = {
          left: event.pageX + "px",
          top: event.pageY + "px",
        };
        this.menuVisible = true;
      });

      // 点击画布空白区域
      graph.on("blank:click", () => {
        graph.cleanSelection && graph.cleanSelection();
        // 关闭右键菜单
        this.menuVisible = false;
        this.assembly = "";
        this.$store.commit("SET_INTRODUCTION", "");
      });

      /**
       * 基础操作
       */
      // // copy cut paste ...
      // graph.bindKey(['meta+c', 'ctrl+c'], () => {
      //     const cells = graph.getSelectedCells()
      //     if (cells.length) {
      //         graph.copy(cells)
      //     }
      //     return false
      // })
      //
      // graph.bindKey(['meta+x', 'ctrl+x'], () => {
      //     const cells = graph.getSelectedCells()
      //     if (cells.length) {
      //         graph.cut(cells)
      //     }
      //     return false
      // })
      // graph.bindKey(['meta+v', 'ctrl+v'], () => {
      //     if (!graph.isClipboardEmpty()) {
      //         const cells = graph.paste({offset: 32})
      //         graph.cleanSelection()
      //         graph.select(cells)
      //     }
      //     return false
      // })
      //
      // //undo redo
      // graph.bindKey(['meta+z', 'ctrl+z'], () => {
      //     if (graph.history.canUndo()) {
      //         graph.history.undo()
      //     }
      //     return false
      // })
      // graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
      //     if (graph.history.canRedo()) {
      //         graph.history.redo()
      //     }
      //     return false
      // })
      //
      // // select all
      // graph.bindKey(['meta+alt+a', 'ctrl+alt+a'], () => {
      //     const nodes = graph.getNodes()
      //     if (nodes) {
      //         graph.select(nodes)
      //     }
      // })
      //
      // // delete
      // graph.bindKey(['backspace', 'delete'], () => {
      //     // 删除选中的元素
      //     const cells = graph.getSelectedCells()
      //     // console.log(cells.isEdge())
      //     if (cells.length) {
      //         graph.removeCells(cells)
      //     }
      // })
      //
      // // zoom
      // graph.bindKey(['ctrl+1', 'meta+1'], () => {
      //     const zoom = graph.zoom()
      //     if (zoom < 1.5) {
      //         graph.zoom(0.1)
      //     }
      // })
      // graph.bindKey(['ctrl+2', 'meta+2'], () => {
      //     const zoom = graph.zoom()
      //     if (zoom > 0.5) {
      //         graph.zoom(-0.1)
      //     }
      // })
    },
    // 连接桩显示/隐藏
    showPorts(show) {
      const container = document.getElementById("container");
      const ports = container.querySelectorAll(".x6-port-body");
      for (let i = 0, len = ports.length; i < len; i = i + 1) {
        ports[i].style.visibility = show ? "visible" : "hidden";
      }
    },
    /**
     * 添加节点
     */
    onAddNode(e) {
      const target = e && e.target.closest(".flow-library-item__img");
      // console.log(e)
      if (target) {
        const id = target.getAttribute("data-id");
        const image = target.getAttribute("data-image");
        const name = target.getAttribute("data-name");
        const shape = target.getAttribute("data-shape");
        const optName = target.getAttribute("data-optName");
        const optCode = target.getAttribute("data-optCode");
        // console.log(shape)
        const nodeOptions =
          shape === SETTING_SHAPE_NAME
            ? {
                shape,
                label: name,
                attrs: {
                  settingImage: {
                    "xlink:href": image,
                  },
                  settingName: {
                    text: name,
                  },
                },
                // 业务数据
                data: {
                  shape,
                  id,
                  optName,
                  optCode,
                },
              }
            : {
                shape,
                width: 180,
                height: 36,
                label: name,
                ports: { ...ports },
                // 业务数据
                data: {
                  shape,
                  id,
                  optName,
                  optCode,
                },
              };
        const newNode = graph.createNode({
          ...nodeOptions,
        });
        dnd.start(newNode, e);
      }
    },
    // 右击
    onMenuClick(select) {
      const [option] = select;
      switch (option) {
        case "name": {
          const color = colors[Math.floor((Math.random() * 100) % 6)];
          selector.attr("settingName/text", color);
          selector.attr("settingName/fill", color);
          selector.attr("text/text", color);
          selector.attr("text/fill", color);
          break;
        }
        case "color": {
          selector.attr(
            "body/fill",
            colors[Math.floor((Math.random() * 100) % 6)]
          );
          selector.attr(
            "body/stroke",
            colors[Math.floor((Math.random() * 100) % 6)]
          );
          break;
        }
        case "remove": {
          // graph.removeNode(selector)
          this.deleteOpr(this.extractNumbers(selector.id));
          break;
        }
        case "input": {
          this.title = "输入文件";
          this.flag = true;
          this.assembly = "输入文件";

          break;
        }
        case "output": {
          this.title = "输出文件";
          this.flag = true;
          this.assembly = "输出文件";
          break;
        }
        case "filterOpera": {
          this.title = "过滤算子";
          this.flag = true;
          this.assembly = "过滤算子";
          break;
        }
        case "attrs": {
          this.title = "属性生成";
          this.flag = true;
          this.assembly = "属性生成";
          break;
        }
        case "standard": {
          this.title = "数据标准化";
          this.flag = true;
          this.assembly = "数据标准化";
          break;
        }
        case "deduplication": {
          this.title = "数据去重";
          this.flag = true;
          this.assembly = "数据去重";
          break;
        }
        case "pca": {
          this.title = "主成分分析";
          this.flag = true;
          this.assembly = "主成分分析";
          break;
        }
        case "default": {
          this.title = "缺省值处理";
          this.flag = true;
          this.assembly = "缺省值处理";
          break;
        }
      }
    },
    /**
     * 保存
     */
    handleSave() {
      const res = graph.toJSON();
      this.rawSource = res;
      graph.toPNG(
        (dataUri) => {
          // 下载
          DataUri.downloadDataUri(
            dataUri,
            `${new Date().toLocaleString()}.chart.png`
          );
        },
        {
          // 导出的图片参数
          width: 1920,
          height: 1080,
          padding: {
            top: 25,
            right: 25,
            bottom: 25,
            left: 25,
          },
        }
      );
      this.$message.success("保存成功");
    },

    // 接口
    /**
     * 根据ID获取特征画布
     */
    async getFeCanvas() {
      const params = {
        canvasId: this.$route.query.id,
      };
      try {
        const resOpr = await this.$store.dispatch(
          "feOperatorList",
          qs.stringify(params, { indices: false })
        );
        const resLine = await this.$store.dispatch(
          "feLineList",
          qs.stringify(params, { indices: false })
        );
        this.resultOpr = resOpr.data || {};
        this.resultLine = resLine.data || {};
        // TODO： 定制后台数据处理
        if (this.resultOpr.success && this.resultLine.success) {
          const dataAry = [
            ...this.resultOpr.data.map((obj) => JSON.parse(obj.coordinate)),
            ...this.resultLine.data.map((obj) => JSON.parse(obj.lineParams)),
          ].filter((item) => {
            if (item) {
              return true;
            }
            return false;
          });

          this.rawSource = {
            cells: dataAry,
          };

          console.log("resultOpr", this.resultOpr);
        } else {
          this.$message.error("读取画布失败");
        }
      } catch (e) {
        console.error(e);
      }
    },

    /**
     * 获取特征画布
     */
    async feOperatorSaveOrUpdate(param) {
      let params;
      const id = this.extractNumbers(param.id);
      const ary = this.resultOpr.data.filter((item) => item.menuId == id);
      console.log(ary.data[ary.data.length - 1]);
      if (ary.length !== 0) {
        params = {
          id: this.getAttribute(ary[0], "id"),
          coordinate: JSON.stringify(param),
        };
      } else {
        params = {
          id: "",
          operatorname: param.shape + id,
          operatorType: param.shape,
          pid: 0,
          menuId: id,
          canvasId: this.$route.query.id,
          coordinate: JSON.stringify(param),
        };
      }
      try {
        const res = await this.$store.dispatch(
          "feOperatorSaveOrUpdate",
          params
        );
        const result = res.data || {};
        // TODO： 定制后台数据处理
        if (result.success) {
          await this.getFeCanvas();
        } else {
          this.$message.error("后台保存失败");
        }
      } catch (e) {
        console.error(e);
      }
    },
    async feLineSaveOrUpdate(param) {
      const params = {
        id: this.extractNumbers(param.id),
        canvasId: this.$route.query.id,
        pid: this.extractNumbers(param.source.cell),
        cid: this.extractNumbers(param.target.cell),
        lineParams: JSON.stringify(param),
      };
      try {
        const res = await this.$store.dispatch("feLineSaveOrUpdate", params);
        const result = res.data || {};
        // TODO： 定制后台数据处理
        if (result.success) {
          await this.getFeCanvas();
        } else {
          this.$message.error("后台保存失败");
        }
      } catch (e) {
        console.error(e);
      }
    },

    async deleteOpr(param) {
      this.$mineConfirm("确认要删除", "删除提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(async () => {
          console.log(param);
          try {
            const ids = this.findObjValue(this.resultOpr.data, "menuId", param);
            const res = await this.$store.dispatch("feOperatorBatchDelete", {
              ids: ids,
            });
            const result = res.data || {};
            if (result.success) {
              // this.$message.success('删除成功');
              graph.removeNode(selector);
            } else {
              this.$message.error(result.msg || "删除失败");
            }
          } catch (e) {
            console.error(e);
          }
        })
        .catch(() => {});
    },

    async deleteLine(param) {
      const ids = param.id;
      const res = await this.$store.dispatch("feLineBatchDelete", { ids: ids });
      const result = res.data || {};
      if (result.success) {
        // this.$message.success('删除成功');
        // graph.removeNode(selector)
        await this.getFeCanvas();
      } else {
        this.$message.error(result.msg || "删除失败");
      }
    },

    // utils
    extractNumbers(str) {
      let numbers, extractedNumbers, result;
      if (str.match) {
        numbers = str.match(/\d+/g);
        extractedNumbers = numbers.map(Number);
        result = Number(extractedNumbers.join("").slice(0, 10));
        return result;
      }
      return str;
    },
    findObjValue(obj, findVal, target) {
      // console.log(obj.find(item => item[findVal] == target))
      if (obj.find((item) => item[findVal] == target)["id"]) {
        return obj.find((item) => item[findVal] == target)["id"];
      }
      return null;
    },
    getAttribute(obj, attr) {
      if (obj && obj[attr]) {
        return obj[attr];
      } else {
        return null;
      }
    },
  },
};
</script>

